package com.yeskery.nut.extend.auth;

import com.yeskery.nut.core.*;
import com.yeskery.nut.plugin.InterceptorPlugin;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 抽象的认证插件
 * @author sprout
 * 2022-06-02 10:45
 */
public abstract class AbstractAuthPlugin implements InterceptorPlugin {

    /** 无需认证路径集合 */
    private final Set<AuthPath> unAuthPaths = new HashSet<>();

    /** 认证路径集合 */
    private final Set<AuthPath> authPaths = new HashSet<>();

    /**
     * 设置无需认证路径
     * @param path 无需认证路径
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addNoAuthPath(String path) {
        return addNoAuthPath(path, !path.contains(AntPath.PATTERN_SYMBOL));
    }

    /**
     * 设置无需认证路径
     * @param paths 无需认证路径数组
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addNoAuthPath(String... paths) {
        for (String path : paths) {
            addNoAuthPath(path);
        }
        return this;
    }

    /**
     * 设置无需认证路径
     * @param path 无需认证路径
     * @param equivalent 是否等值比较，<code>true</code>进行等值比较，<code>false</code>进行前缀比较
     * @param methods HTTP方法
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addNoAuthPath(String path, boolean equivalent, Method... methods) {
        return doAddAuthPath(unAuthPaths, path, equivalent, methods);
    }

    /**
     * 设置无需认证路径
     * @param path 无需认证路径
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addNoAuthPath(AntPath path) {
        return addNoAuthPath(path, !path.isPatternModel());
    }

    /**
     * 设置无需认证路径
     * @param paths 无需认证路径数组
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addNoAuthPath(AntPath... paths) {
        for (AntPath path : paths) {
            addNoAuthPath(path);
        }
        return this;
    }

    /**
     * 设置无需认证路径
     * @param path 无需认证路径
     * @param equivalent 是否等值比较，<code>true</code>进行等值比较，<code>false</code>进行前缀比较
     * @param methods HTTP方法
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addNoAuthPath(AntPath path, boolean equivalent, Method... methods) {
        return doAddAuthPath(unAuthPaths, path, equivalent, methods);
    }

    /**
     * 设置认证路径
     * @param path 认证路径
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addAuthPath(String path) {
        return addAuthPath(path, !path.contains(AntPath.PATTERN_SYMBOL));
    }

    /**
     * 设置认证路径
     * @param paths 认证路径数组
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addAuthPath(String... paths) {
        for (String path : paths) {
            addAuthPath(path);
        }
        return this;
    }

    /**
     * 设置认证路径
     * @param path 认证路径
     * @param equivalent 是否等值比较，<code>true</code>进行等值比较，<code>false</code>进行前缀比较
     * @param methods HTTP方法
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addAuthPath(String path, boolean equivalent, Method... methods) {
        return doAddAuthPath(authPaths, path, equivalent, methods);
    }

    /**
     * 设置认证路径
     * @param path 认证路径
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addAuthPath(AntPath path) {
        return addAuthPath(path, !path.isPatternModel());
    }

    /**
     * 设置认证路径
     * @param paths 认证路径数组
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addAuthPath(AntPath... paths) {
        for (AntPath path : paths) {
            addAuthPath(path);
        }
        return this;
    }

    /**
     * 设置认证路径
     * @param path 认证路径
     * @param equivalent 是否等值比较，<code>true</code>进行等值比较，<code>false</code>进行前缀比较
     * @param methods HTTP方法
     * @return 认证插件对象
     */
    public AbstractAuthPlugin addAuthPath(AntPath path, boolean equivalent, Method... methods) {
        return doAddAuthPath(authPaths, path, equivalent, methods);
    }

    @Override
    public boolean beforeHandle(Request request, Response response, Execution execution, Controller controller) throws Exception {
        for (AuthPath authPath : authPaths) {
            if (authPath.equivalent) {
                if (authPath.path.equals(new Path(request.getPath()))
                        && authPath.methods.stream().anyMatch(m -> m == Method.ALL || m == request.getMethod())) {
                    return false;
                }
            } else {
                if (authPath.path.match(request.getPath())
                        && authPath.methods.stream().anyMatch(m -> m == Method.ALL || m == request.getMethod())) {
                    return false;
                }
            }
        }

        for (AuthPath unAuthPath : unAuthPaths) {
            if (unAuthPath.equivalent) {
                if (unAuthPath.path.equals(new AntPath(request.getPath()))
                        && unAuthPath.methods.stream().anyMatch(m -> m == Method.ALL || m == request.getMethod())) {
                    return true;
                }
            } else {
                if (unAuthPath.path.match(request.getPath())
                        && unAuthPath.methods.stream().anyMatch(m -> m == Method.ALL || m == request.getMethod())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 设置认证路径
     * @param authPaths 认证路径
     * @param path 认证路径
     * @param equivalent 是否等值比较，<code>true</code>进行等值比较，<code>false</code>进行前缀比较
     * @param methods HTTP方法
     * @return 认证插件对象
     */
    private AbstractAuthPlugin doAddAuthPath(Set<AuthPath> authPaths, AntPath path, boolean equivalent, Method... methods) {
        if (methods.length == 0 || Arrays.stream(methods).anyMatch(m -> m == Method.ALL)) {
            authPaths.add(new AuthPath(path, equivalent, Collections.singleton(Method.ALL)));
        } else {
            authPaths.add(new AuthPath(path, equivalent, Arrays.stream(methods).collect(Collectors.toSet())));
        }
        return this;
    }

    /**
     * 设置认证路径
     * @param authPaths 认证路径
     * @param path 认证路径
     * @param equivalent 是否等值比较，<code>true</code>进行等值比较，<code>false</code>进行前缀比较
     * @param methods HTTP方法
     * @return 认证插件对象
     */
    private AbstractAuthPlugin doAddAuthPath(Set<AuthPath> authPaths, String path, boolean equivalent, Method... methods) {
        if (methods.length == 0 || Arrays.stream(methods).anyMatch(m -> m == Method.ALL)) {
            authPaths.add(new AuthPath(new AntPath(path), equivalent, Collections.singleton(Method.ALL)));
        } else {
            authPaths.add(new AuthPath(new AntPath(path), equivalent, Arrays.stream(methods).collect(Collectors.toSet())));
        }
        return this;
    }

    /**
     * 未认证路径对象
     * @author sprout
     * @version 1.0
     * 2022-05-30 22:26
     */
    protected static class AuthPath {

        /** 路径 */
        private final AntPath path;

        /** 是否等值比较 */
        private final boolean equivalent;

        /** HTTP请求方法 */
        private final Set<Method> methods;

        /**
         * 构建一个未认证路径对象
         * @param path 路径
         * @param equivalent 是否等值比较
         * @param methods 支持的请求方法集合
         */
        public AuthPath(AntPath path, boolean equivalent, Set<Method> methods) {
            if (path == null) {
                throw new IllegalArgumentException("Path Must Not Be Null.");
            }
            this.path = path;
            this.equivalent = equivalent;
            this.methods = methods;
        }

        @Override
        public int hashCode() {
            return this.path.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            AuthPath unLoginPath = (AuthPath) o;
            return path.equals(unLoginPath.path);
        }
    }
}
